//
// ETW log writer implementation.

#include "etw_log_writer.h"

// {EE32CFB6-67F3-4f30-A514-F7CFEFBEDC75}
const GUID EtwLogWriter::kLKAgentTraceGuid = 
{ 0xee32cfb6, 0x67f3, 0x4f30, { 0xa5, 0x14, 0xf7, 0xcf, 0xef, 0xbe, 0xdc, 0x75 } };


// {4DFAA38B-988F-4b3f-A334-47DC1366314B}
const GUID EtwLogWriter::kLogEventId = 
{ 0x4dfaa38b, 0x988f, 0x4b3f, { 0xa3, 0x34, 0x47, 0xdc, 0x13, 0x66, 0x31, 0x4b } };


EtwLogWriter::EtwLogWriter(const GUID& provider_guid) : 
		EtwTraceProvider(provider_guid), rtl_capture_stack_backtrace_(NULL)
{
	HMODULE kernel32 = ::GetModuleHandle(L"kernel32.dll");
	if (kernel32 != NULL)
		rtl_capture_stack_backtrace_ = reinterpret_cast<RtlCaptureStackBackTraceFunc>(
			::GetProcAddress(kernel32, "RtlCaptureStackBackTrace"));

	EtwTraceProvider::Register();
}

void EtwLogWriter::Cleanup()
{
	EtwTraceProvider::Unregister();
}

EtwLogWriter* EtwLogWriter::Create()
{
	return new EtwLogWriter(kLKAgentTraceGuid);
}

bool EtwLogWriter::WantsToLogRegardless() const
{
	return session_handle() != 0;
}

bool EtwLogWriter::IsCatLevelEnabled(LogCategory category, LogLevel level) const
{
	if ((enable_flags() & CategoryToEnableFlag(category)) == 0 ||
		enable_level() < LogLevelToTraceLevel(level))
		return false;
	return true;
}

void EtwLogWriter::OutputMessage(const OutputInfo* output_info)
{
	if (!IsCatLevelEnabled(output_info->category, output_info->level))
		return;

	CStringA msg1(output_info->msg1);
	CStringA msg2(output_info->msg2);
	EtwEventLevel level = LogLevelToTraceLevel(output_info->level);
	if (enable_flags() & kCaptureStackTraceMask)
	{
		void* back_trace[32] = {0};
		DWORD depth = 0;

		if (rtl_capture_stack_backtrace_)
			depth = rtl_capture_stack_backtrace_(0, arraysize(back_trace), back_trace, NULL);

		EtwMofEvent<4> mof_event(kLogEventId, kLogMessageWithStackTraceType, level);
		mof_event.SetField(0, sizeof(depth), &depth);
		mof_event.SetField(1, depth * sizeof(back_trace[0]), &back_trace);
		mof_event.SetField(2, msg1.GetLength(), msg1.GetString());
		mof_event.SetField(3, msg2.GetLength() + 1, msg2.GetString());
		Log(mof_event.get());
	}
	else
	{
		EtwMofEvent<2> mof_event(kLogEventId, kLogMessageType, level);
		mof_event.SetField(0, msg1.GetLength(), msg1.GetString());
		mof_event.SetField(1, msg2.GetLength() + 1, msg2.GetString());
		Log(mof_event.get());
	}
}

void EtwLogWriter::OnEventsEnabled()
{
	CORE_LOG(L2, (_T("ETW logging enabled")));
}

void EtwLogWriter::OnEventsDisabled()
{
	CORE_LOG(L2, (_T("ETW logging disabled")));
}

EtwEventFlags EtwLogWriter::CategoryToEnableFlag(LogCategory category)
{
	// Bit zero is reserved for the capture stack trace enable flag.
	return 1 << (category + 1);
}

EtwEventLevel EtwLogWriter::LogLevelToTraceLevel(LogLevel level)
{
	switch (level)
	{
	case LEVEL_FATALERROR:
		return TRACE_LEVEL_FATAL;
	case LEVEL_ERROR:
		return TRACE_LEVEL_ERROR;
	case LEVEL_WARNING:
		return TRACE_LEVEL_WARNING;
	case L1:
	case L2:
		return TRACE_LEVEL_INFORMATION;
	case L3:
	case L4:
	case L5:
	case L6:
		return TRACE_LEVEL_VERBOSE;

	case LEVEL_ALL:
	default:
		return TRACE_LEVEL_NONE;
	}
}